package com.ting.etp.host.starter;

import com.ting.etp.host.autoconfig.EtpPluginAutoConfiguration;
import com.ting.etp.host.loader.EtpPluginLoader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.GenericApplicationContext;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author zifang
 */
@Slf4j
public class EtpAutoPluginApplication implements ApplicationContextAware, ApplicationListener<ApplicationStartedEvent> {

    private ApplicationContext applicationContext;
    private EtpPluginLoader etpPluginLoader;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 是 Spring Boot 提供的一个应用生命周期事件，它在 Spring Boot 应用启动完成（但尚未启动内置的 Web 服务器，如 Tomcat 或 Jetty）时触发。以下是它的核心作用和使用场景：
     * 1. 事件触发时机
     * 阶段：在 ApplicationContext 完全初始化后，但在调用 CommandLineRunner 或 ApplicationRunner 之前触发。
     * 对比其他事件：
     * ApplicationStartingEvent：最早的事件，在 ApplicationContext 创建前触发。
     * ApplicationEnvironmentPreparedEvent：环境准备完成时触发。
     * ApplicationPreparedEvent：ApplicationContext 准备完成但未刷新时触发。
     * ApplicationStartedEvent：ApplicationContext 已刷新，但服务器未启动。
     * ApplicationReadyEvent：服务器启动完成，应用完全就绪时触发。
     * ApplicationFailedEvent：启动失败时触发。
     * 2. 核心作用
     * 允许在服务器启动前执行逻辑：
     * 适合需要访问完全初始化的 ApplicationContext，但又不希望逻辑被 CommandLineRunner 或 ApplicationRunner 拦截的场景（例如，修改配置、注册额外 Bean 等）。
     * 与 ApplicationRunner 的区别：
     * ApplicationRunner 的逻辑会在 ApplicationStartedEvent 之后执行，且更侧重于命令行参数处理。而 ApplicationStartedEvent 更底层，适合框架级别的扩展。
     * 3. 典型使用场景
     * 动态修改 Bean 配置：
     * 在应用启动后、服务器启动前，动态调整某些 Bean 的属性。
     * 集成第三方库的初始化：
     * 某些库需要在 Spring 上下文就绪后、服务器启动前执行初始化代码。
     * 调试或日志记录：
     * 记录应用启动完成的关键信息（如加载的 Bean 列表）。
     * 5. 注意事项
     * 服务器未启动：此时内置服务器（如 Tomcat）尚未启动，因此无法处理 HTTP 请求。
     * 与 ApplicationReadyEvent 的选择：
     * 如果需要确保服务器已启动，应使用 ApplicationReadyEvent（例如，发送通知告知应用已就绪）。
     * @param event
     */
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        initialize(applicationContext);
    }

    private final AtomicBoolean beInitialized = new AtomicBoolean(false);

    private void initialize(ApplicationContext applicationContext){
        Objects.requireNonNull(applicationContext, "ApplicationContext can't be null");
        if(beInitialized.get()){
            throw new RuntimeException("Plugin has been initialized");
        }
        // 获取Configuration
        EtpPluginAutoConfiguration configuration = getConfiguration(applicationContext);
        // 检查配置
//        configuration.checkConfig();

//        createPluginUser(applicationContext);
        createEtpPluginLoader(applicationContext);
        try {
//            if(!(pluginOperator instanceof PluginOperatorWrapper)){
//                pluginOperator = new PluginOperatorWrapper(pluginOperator, configuration);
//            }
            GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext;
            DefaultListableBeanFactory defaultListableBeanFactory = genericApplicationContext.getDefaultListableBeanFactory();
            setPluginOperator(defaultListableBeanFactory);
            setPluginUser(defaultListableBeanFactory);
            initExtractFactory(genericApplicationContext);
            etpPluginLoader.initPlugins();
            beInitialized.set(true);
        } catch (Exception e) {
            log.error("初始化插件异常.",e);
        }
    }

    /**
     * 创建插件操作者。子类可扩展
     * @param applicationContext Spring ApplicationContext
     * @return pluginOperator
     */
    protected synchronized EtpPluginLoader createEtpPluginLoader(ApplicationContext applicationContext){
        if(etpPluginLoader == null){
            etpPluginLoader = applicationContext.getBean(EtpPluginLoader.class);
        }
        return  etpPluginLoader;
    }

    private EtpPluginAutoConfiguration getConfiguration(ApplicationContext applicationContext){
        EtpPluginAutoConfiguration configuration = null;
        try {
            configuration = applicationContext.getBean(EtpPluginAutoConfiguration.class);
        } catch (Exception e){
            // no show exception
        }
        if(configuration == null){
            throw new BeanCreationException("没有发现 <EtpPluginAutoConfiguration> Bean, " +
                    "请在 Spring 容器中将 <EtpPluginAutoConfiguration> 定义为Bean");
        }
        return configuration;
    }

    /**
     * 初始化扩展工厂
     * @param applicationContext applicationContext
     */
    private void initExtractFactory(GenericApplicationContext applicationContext){
//        ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
//        DefaultExtractFactory defaultExtractFactory = (DefaultExtractFactory)ExtractFactory.getInstant();
//        initMainExtract((OpExtractFactory)defaultExtractFactory.getTarget(), beanFactory);
    }

    /**
     * 直接将 PluginOperator 和 PluginUser 注入到ApplicationContext容器中
     * @param defaultListableBeanFactory DefaultListableBeanFactory
     */
    protected void setPluginOperator(DefaultListableBeanFactory defaultListableBeanFactory){
//        defaultListableBeanFactory.registerSingleton(pluginOperator.getClass().getName(), pluginOperator);
    }

    /**
     * 直接将 PluginOperator 和 PluginUser 注入到ApplicationContext容器中
     * @param defaultListableBeanFactory DefaultListableBeanFactory
     */
    protected void setPluginUser(DefaultListableBeanFactory defaultListableBeanFactory){
//        defaultListableBeanFactory.registerSingleton(pluginUser.getClass().getName(), pluginUser);
    }

}
